/*-------------------------------------------------------------------------
 *
 * ip.c
 *      IPv6-aware network access.
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/common/ip.c
 *
 * This file and the IPV6 implementation were initially provided by
 * Nigel Kukard <nkukard@lbsd.net>, Linux Based Systems Design
 * http://www.lbsd.net.
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#include <sys/file.h>

#include "common/ip.h"



#ifdef    HAVE_UNIX_SOCKETS
static int getaddrinfo_unix(const char *path,
                 const struct addrinfo *hintsp,
                 struct addrinfo **result);

static int getnameinfo_unix(const struct sockaddr_un *sa, int salen,
                 char *node, int nodelen,
                 char *service, int servicelen,
                 int flags);
#endif


/*
 *    pg_getaddrinfo_all - get address info for Unix, IPv4 and IPv6 sockets
 */
int
pg_getaddrinfo_all(const char *hostname, const char *servname,
                   const struct addrinfo *hintp, struct addrinfo **result)
{
    int            rc;

    /* not all versions of getaddrinfo() zero *result on failure */
    *result = NULL;

#ifdef HAVE_UNIX_SOCKETS
    if (hintp->ai_family == AF_UNIX)
        return getaddrinfo_unix(servname, hintp, result);
#endif

    /* NULL has special meaning to getaddrinfo(). */
    rc = getaddrinfo((!hostname || hostname[0] == '\0') ? NULL : hostname,
                     servname, hintp, result);

    return rc;
}


/*
 *    pg_freeaddrinfo_all - free addrinfo structures for IPv4, IPv6, or Unix
 *
 * Note: the ai_family field of the original hint structure must be passed
 * so that we can tell whether the addrinfo struct was built by the system's
 * getaddrinfo() routine or our own getaddrinfo_unix() routine.  Some versions
 * of getaddrinfo() might be willing to return AF_UNIX addresses, so it's
 * not safe to look at ai_family in the addrinfo itself.
 */
void
pg_freeaddrinfo_all(int hint_ai_family, struct addrinfo *ai)
{
#ifdef HAVE_UNIX_SOCKETS
    if (hint_ai_family == AF_UNIX)
    {
        /* struct was built by getaddrinfo_unix (see pg_getaddrinfo_all) */
        while (ai != NULL)
        {
            struct addrinfo *p = ai;

            ai = ai->ai_next;
            free(p->ai_addr);
            free(p);
        }
    }
    else
#endif                            /* HAVE_UNIX_SOCKETS */
    {
        /* struct was built by getaddrinfo() */
        if (ai != NULL)
            freeaddrinfo(ai);
    }
}


/*
 *    pg_getnameinfo_all - get name info for Unix, IPv4 and IPv6 sockets
 *
 * The API of this routine differs from the standard getnameinfo() definition
 * in two ways: first, the addr parameter is declared as sockaddr_storage
 * rather than struct sockaddr, and second, the node and service fields are
 * guaranteed to be filled with something even on failure return.
 */
int
pg_getnameinfo_all(const struct sockaddr_storage *addr, int salen,
                   char *node, int nodelen,
                   char *service, int servicelen,
                   int flags)
{
    int            rc;

#ifdef HAVE_UNIX_SOCKETS
    if (addr && addr->ss_family == AF_UNIX)
        rc = getnameinfo_unix((const struct sockaddr_un *) addr, salen,
                              node, nodelen,
                              service, servicelen,
                              flags);
    else
#endif
        rc = getnameinfo((const struct sockaddr *) addr, salen,
                         node, nodelen,
                         service, servicelen,
                         flags);

    if (rc != 0)
    {
        if (node)
            strlcpy(node, "???", nodelen);
        if (service)
            strlcpy(service, "???", servicelen);
    }

    return rc;
}


#if defined(HAVE_UNIX_SOCKETS)

/* -------
 *    getaddrinfo_unix - get unix socket info using IPv6-compatible API
 *
 *    Bugs: only one addrinfo is set even though hintsp is NULL or
 *          ai_socktype is 0
 *          AI_CANONNAME is not supported.
 * -------
 */
static int
getaddrinfo_unix(const char *path, const struct addrinfo *hintsp,
                 struct addrinfo **result)
{// #lizard forgives
    struct addrinfo hints;
    struct addrinfo *aip;
    struct sockaddr_un *unp;

    *result = NULL;

    MemSet(&hints, 0, sizeof(hints));

    if (strlen(path) >= sizeof(unp->sun_path))
        return EAI_FAIL;

    if (hintsp == NULL)
    {
        hints.ai_family = AF_UNIX;
        hints.ai_socktype = SOCK_STREAM;
    }
    else
        memcpy(&hints, hintsp, sizeof(hints));

    if (hints.ai_socktype == 0)
        hints.ai_socktype = SOCK_STREAM;

    if (hints.ai_family != AF_UNIX)
    {
        /* shouldn't have been called */
        return EAI_FAIL;
    }

    aip = calloc(1, sizeof(struct addrinfo));
    if (aip == NULL)
        return EAI_MEMORY;

    unp = calloc(1, sizeof(struct sockaddr_un));
    if (unp == NULL)
    {
        free(aip);
        return EAI_MEMORY;
    }

    aip->ai_family = AF_UNIX;
    aip->ai_socktype = hints.ai_socktype;
    aip->ai_protocol = hints.ai_protocol;
    aip->ai_next = NULL;
    aip->ai_canonname = NULL;
    *result = aip;

    unp->sun_family = AF_UNIX;
    aip->ai_addr = (struct sockaddr *) unp;
    aip->ai_addrlen = sizeof(struct sockaddr_un);

    strcpy(unp->sun_path, path);

#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
    unp->sun_len = sizeof(struct sockaddr_un);
#endif

    return 0;
}

/*
 * Convert an address to a hostname.
 */
static int
getnameinfo_unix(const struct sockaddr_un *sa, int salen,
                 char *node, int nodelen,
                 char *service, int servicelen,
                 int flags)
{// #lizard forgives
    int            ret = -1;

    /* Invalid arguments. */
    if (sa == NULL || sa->sun_family != AF_UNIX ||
        (node == NULL && service == NULL))
        return EAI_FAIL;

    if (node)
    {
        ret = snprintf(node, nodelen, "%s", "[local]");
        if (ret == -1 || ret > nodelen)
            return EAI_MEMORY;
    }

    if (service)
    {
        ret = snprintf(service, servicelen, "%s", sa->sun_path);
        if (ret == -1 || ret > servicelen)
            return EAI_MEMORY;
    }

    return 0;
}
#endif                            /* HAVE_UNIX_SOCKETS */
